perm filename SMALLB.PAL[HAL,HE]4 blob sn#157819 filedate 1975-05-06 generic text, type C, neo UTF8
COMMENT ⊗   VALID 00017 PAGES
C REC  PAGE   DESCRIPTION
C00001 00001
C00002 00002	.SBTTL SMALL BLOCK ALLOCATOR
C00006 00003	 Definitions of fields
C00009 00004	 DEFSPC
C00011 00005	 DATA AREA
C00012 00006	 MAPPTR, MARKR0, LNKMTH
C00016 00007	 MARKPH, MKPHRT, MKROUT
C00018 00008	ROUTINE CPFYSP,<SPC>
C00022 00009	ROUTINE CPFY
C00023 00010	ROUTINE SWEEP
C00026 00011	ROUTINE GC
C00027 00012	 GETSBK, GETBLK
C00030 00013	 FREBLK, FRESBK
C00032 00014	 NEWSPC, SETSPC
C00034 00015	ROUTINE ADDBUF,<SPACE>
C00036 00016	 Standard spaces, SBINIT
C00038 00017	.IFNZ	SMBDBG		Test routine
C00040 ENDMK
C⊗;
.SBTTL SMALL BLOCK ALLOCATOR
;Coded by RHT 9-Sept-1974

SMBDBG == 1	;WE ARE DEBUGGING

COMMENT ⊗

Overview: The basic idea is to break up large blocks of storage into
smaller, fixed size blocks, and then administer them.  The routines
given here provide a facility whereby a user can have a number of
different "spaces" of fixed size blocks.  Each space is described by
an approximately 10 word space descriptor.  All these space
descriptors are linked together on a big chain (SIDLST), and each
space is assumed to have asociated with it a unique 8-bit number
(thus allowing up to 256 spaces).  Each space descriptor owns a
linked list of buffers; each buffer contains a number of blocks.
Each space may be either collectable or uncollectable.  Any block may
be released explicitly, although if the space is collectable, this
may be unwise.  Also, collectable spaces are compactified by the
garbage collector.  As an efficiency measure, the first few indices
[of what? - RF] (now, 1-10) are also kept in a table (SIDTBL). 
 
 Blocks are allocated by the routines GETBLK & GETSBK:
 
 	MOV	#IDCODE,R0	 ;IDCODE is the 8-bit code for a space
 	JSR	PC,GETBLK	 ;
 
 	MOV	#SPCDSC,R0	 ;SPCDSC is the address of the space
 	JSR	PC,GETSBK	 ;descriptor
 
In either case, a pointer to a new block is returned in R0.  If need
be, the free space routine will call the garbage collector to get
more space or (if the space is not collectable or garbage collection
is disabled) it will call the large block routines to get another
buffer.  If garbage collection fails to produce a goodly surplus of
blocks for some space, then additional buffers of new blocks will be
obtained. 
 
Each small block has the following format:

 		TAB,,ID		 tag is used in garbage collecting
 	R0 →→	WORD 0		 this is the word pointed to by getblk
 		:
 		WORD n
 
Blocks are zeroed before being returned.  Although this is sometimes
a bit extra overhead, it does prevent bugs and avoids the necessity
for explicit clears all over the place. 
 
Blocks are freed by the routines FREBLK & FRESBK:
 
 	MOV	BLOCK,R0	 ;R0 ← block to free
 	JSR	PC,FREBLK
 
 	MOV	BLOCK,R0	 ;R0 ← block to free
 	MOV	#SPCDSC,R1	 ;R1 ← space descriptor
 	JSR	PC,FRESBK
 
The macro 
 	 DEFSPC ID,MMRT,SZ,NPB,GCF,NMN,NPC
may be used to declare compiled-in space descriptors.  Please see the
comment on routine MAPPTR for additional instuctions for declaring
spaces. 
⊗
; Definitions of fields

;SPACE DESCRIPTOR

	II == 0
	XX	IDFLAG	;Actually a byte; gets put in the ID part of tag word
	XX	MAPRTN	;Routine to be called when marking
	XX	SIZE	;How many words for a value cell in this type block.
	XX	NPERB	;Number of blocks per buffer
	XX	GCFG	;Set if this is not a collectable area
	XX	NMIN	;Min number of free blocks to be returned by GC
	XX	NPCT	;Min % of free blocks to be returned by GC
	XX	NXTSID	;Next space descriptor on ID chain
	XX	FFREE	;Free list [??? - RF]
	XX	FSTBUF	;Oldest buffer
	XX	LSTBUF	;Newest buffer
	XX	NALLOC	;Number of buffers allocated
	XX	NFREE	;Number of buffers free
	SPCHDR == II	;Number of bytes in a space descriptor

; BUFFER HEADER
	II == 0
	XX	NXTBUF	;Next buffer in this space
	XX	PRVBUF	;Previous buffer in this space
	XX	LSTBLK	;Address of last block in this buffer
	XX	FSTBLK	;Address of first block in this buffer, word 0.
	BUFHDR == II	;Number of bytes in a buffer header

; SMALL BLOCK
	II == 0
	TAG == -1	; ≠ 0 means in use (used by GC)
	TAGID == -2	;Holds an "ID" for this record
	XX	WORD0	;First data word

; GC METHODS
	II == 0
	XX	METH	;Address of routine to call
	XX	NXTMTH	;Next CG method on chain

; Marking method macro
       .MACRO MMETH ROUT
	ROUT
	0
       .ENDM
; DEFSPC

; Assemble-time spaces
       .IF2
	SIDHED == SIDCHN ;Sets SIDHED to the final value of SIDCHN
       .ENDC

SIDCNT == 0		;Number of assembled-in space descriptors
SIDCHN == 0		;Linkage for assembled-in space descriptors

COMMENT ⊗ Declare assembled-in space descriptors: Makes a space
descriptor.  ID is given the number of the space.  MMRT is the map
routine, SZ the size, NPB the number of blocks per buffer, GCF is set
if the area is not to be collected, NMN is the minimum number of free
blocks that GC should return, NPC is the minimum percent of free
blocks that GC should return.  ⊗

.MACRO DEFSPC ID,MMRT,SZ,NPB,GCF,NMN,NPC
    .IFNDF ID
	SIDCNT==SIDCNT+1
	ID==SIDCNT
    .ENDC
    II==.
    .BLKW SPCHDR/2
	TT	IDFLAG,ID
	TT	MAPRTN,MMRT
	TT	SIZE,SZ
	TT	NPERB,NPB
	TT	GCFG,GCF
	TT	NMIN,NMN
	TT	NPCT,NPC
	TT	NXTSID,SIDCHN
	TT	FFREE,0
	TT	FSTBUF,0
	TT	LSTBUF,0
	TT	NALLOC,0
	TT	NFREE,0
    SIDCHN == II
    .=II+SPCHDR
      .IF2
	.IFGE MAXIDF-ID
	  PUTLOC <ID*2 + SIDTBL>,SIDCHN
	.ENDC
      .ENDC
.ENDM

; DATA AREA

MMETHS:	0		;Header of list of marking methods
GCOK:	0		;Set if GC is OK now
CPFYOK:	0		;Set if compactification is OK
SIDLST:			;List of space descriptor blocks
	.IF1		;Let pass 2 of assemble fix this up
		0
	.ENDC
	.IF2
		SIDHED
	.ENDC

MAXIDF == 30		;Max index into SIDTBL
SIDTBL:	0		;Table of space descriptors for efficiency
	.BLKB MAXIDF
; MAPPTR, MARKR0, LNKMTH

ROUTINE MAPPTR,<ROUT>	
 
COMMENT ⊗ MAPPTR takes a single parameter (in R0) which is a pointer
to a small block.  It returns (in R0) a pointer value which is to be
stored back in the pointer cell.  

MAPPTR runs down a list of "marking methods" (MMETHS).  Each method
is assumed to be responsible for some batch of pointers.  For each
pointer it finds, a method should call the routine MARKR0 (via JSR
PC).  Thus, each marking method should have the form

 	METH:	R←#<first pointer>
 		WHILE R≠NULL DO
 			BEGIN
 			R0←(R);
 			JSR PC,MARKR0;
 			(R)←R0;
 			R←#<next pointer>;
 			END;
 		RETURN;
 
MARKR0 determines the type of the record (finds its space descriptor).
It then does a 
 		JSR	PC,@MAPRTN(<space>)
for spaces where there are no pointer subfields; this may be just
MKRTJM (ie, a JMP @2(RF) ).  If there are pointer subfields, then the
MAPRTN needs to be more complicated:
 
 		IF TAG(R0) THEN RTS PC;
 		JSR	PC,@2(RF);
 		PUSH R;
 		R←R0;
 		∀ <field> | <field> is a pointer subfield of R DO
 			BEGIN
 			R0←<field>
 			JSR	PC,MARKR0;
 			<field>←R0;
 			end;
 		R0←R;
 		POP R;
 		RTS PC;
 
Note: it may be a good idea to change the conventions here a bit to
(1) pass a pointer at a record pointer & (2) let markr0 assume
responsibility for storing the updated pointer.  The advantage of
such a course is that it allows iterative marking of long lists, thus
avoiding possible pdl overflows. 
⊗

;MAPPTR:	;(IN CASE YOU HAD FORGOTTEN)
	MOV	R2,-(SP)	;
	MOV	MMETHS,R2	;LIST OF MARKING METHS
	BEQ	MAPRTS		;DONE??
MAPLP:	CALL	@METH(R2),<ROUT(RF)>
	MOV	NXTMTH(R2),R2	;NEXT METHOD
	BNE	MAPLP		;ITERATE
MAPRTS:	MOV	(SP)+,R2	;
	RTS	RF		;RETURN

MKRTJM:	JMP	@ROUT(RF)	;THIS IS THE APPROPRIATE 
				;MARKING INTRINSIC FOR CASES WHERE
				;THERE ARE NO POINTER SUBFIELDS

MARKR0:	TST	R0		;A NULL IS A NULL
	BEQ	MR0.X		;	IS A NULL
	JSR	PC,PTRSID	;GETS SPACE DESCRIPTOR INTO R1
	JSR	PC,@MAPRTN(R1)	;CALL APPROPRIATE MARKING INTRINSIC
MR0.X:	RTS	PC

; Add a method to the "MMETHS" list:
LNKMTH:	MOV	MMETHS,NXTMTH(R0)
	MOV	R0,MMETHS
	RTS	PC
; MARKPH, MKPHRT, MKROUT

ROUTINE MARKPH		
	MOV	R2,-(SP)	;
	MOV	R3,-(SP)	;
	MOV	SIDLST,R2	;ALL SIZES
	BEQ	MKPHRT		;DONE ALREADY??
MKPH.1:	TST	GCFG(R2)	;A GC SPACE??
	BEQ	MKPH.AD		;NO, GO ON TO NEXT
	MOV	SIZE(R2),R3	;
	INC	R3		;ONE FOR TAG WORD
	ASL	R3		;WORDS TO BYTES
	MOV	FSTBUF(R2),R1	;CLEAR THIS BUFFER
MKP.02:	MOV	FSTBLK(R1),R0	;FIRST BLOCK
MKPH.2:	CMP	R0,LSTBLK(R1)	;DONE THIS BUFFER?
	BGT	MKPH.3		;IF SO, GO ON TO NEXT
	CLRB	TAG(R0)		;CLEAR TAG
	ADD	R3,R0		;BUMP POINTER TO NEXT
	BR	MKPH.2		;ITERATE
MKPH.3:	MOV	NXTBUF(R1),R1	;ON TO NEXT BUFFER
	BNE	MKP.02		;IF WE HAVE ONE
MKPH.AD:MOV	NXTSID(R2),R2	;GO ON TO NEXT SPACE
	BNE	MKPH.1		;

	CALL	MAPPTR,<#MKROUT> ;DO THE ACTUAL MARKING
	
MKPHRT:	MOV	(SP)+,R3	;RESTORE
	MOV	(SP)+,R2
	RTS	RF

MKROUT:	MOVB	#377,TAG(R0)	;
	RTS	PC		;

ROUTINE CPFYSP,<SPC>

; Performs all data moving required to compactify one size space

	MOV	R2,-(SP)	;SAVE SOME ACS
	MOV	R3,-(SP)	;
	MOV	R4,-(SP)	;
	MOV	SPC(RF),R2	;SPACE DSCR
	MOV	FSTBUF(R2),R3	;OLDEST
	MOV	LSTBUF(R2),R4	;NEWEST
	JSR	PC,NXF.0	;NEXT FREE INTO 1
				;MAY MODIFY R3
	BEQ	CPFY.2		;NO FREE
	JSR	PC,NXR.0	;GET A RECORD TO MOVE
				;INTO R1 (MAY MUNCH R0)
	BEQ	CPFY.2		;
CPFY.1:	MOV	R1,-(SP)	;SAVE THESE
	MOV	R0,-(SP)	;
	MOVB	#377,TAG(R0)	;
	CLRB	TAG(R1)		;
	MOV	SIZE(R2),R2	;
CPYR:	MOV	(R1)+,(R0)+	;COPY RECORD
	DEC	R2		;COUNT DOWN
	BGT	CPYR		;DONE??
	MOV	SPC(RF),R2	;YES
	MOV	(SP)+,R0	;GET ACS BACK
	MOV	(SP)+,R1	;
	MOV	R0,WORD0(R1)	;POINT AT THIS ONE
	JSR	PC,NXF.NX	;NEXT FREE
	BEQ	CPFY.2
	JSR	PC,NXR.NX	;NEXT RECORD
	BNE	CPFY.1		;PROCESS THAT ONE
CPFY.2:
	MOV	(SP)+,R4	;
	MOV	(SP)+,R3	;
	MOV	(SP)+,R2
	RTS	RF

NXF.0:	MOV	FSTBLK(R3),R0	;FIND A FREE BLOCK
NXF.1:	TSTB	TAG(R0)		;FREE
	BEQ	NXF.4		;YES
NXF.NX:	ADD	SIZE(R2),R0	;LOOK AT NEXT
	ADD	SIZE(R2),R0	;ADD TWICE SINCE WANT TRUE ADDRESS
	TST	(R0)+		;ADD IN TAG WORD OFFSET
	CMP	R0,LSTBLK(R3)	;MORE TO TRY??
	BLE	NXF.1		;TRY AGAIN
	MOV	NXTBUF(R3),R3	;NEXT NEWEST BUFFER
	BEQ	NXF.3		;LOOK THERE
	CMP	R3,R4		;IF NOT TO THE R SUPPLIER
	BNE	NXF.0
NXF.3:	CLR	R0
NXF.4:	MOV	R0,R0		;GET FLAGS CORRECT
	RTS	PC


NXR.0:	MOV	FSTBLK(R4),R0	;FIND A FULL BLOCK
NXR.1:	TSTB	TAG(R0)		;FULL
	BNE	NXF.4		;YES
NXR.NX:	ADD	SIZE(R2),R0	;LOOK AT NEXT
	ADD	SIZE(R2),R0	;ADD TWICE SINCE WANT TRUE ADDRESS
	TST	(R0)+		;ADD IN TAG WORD OFFSET
	CMP	R0,LSTBLK(R4)	;MORE TO TRY??
	BLE	NXR.1		;TRY AGAIN
	MOV	PRVBUF(R4),R4	;NEXT NEWEST BUFFER
	BEQ	NXR.3		;LOOK THERE
	CMP	R3,R4		;IF NOT TO THE R SUPPLIER
	BNE	NXF.0
NXR.3:	CLR	R0
NXR.4:	MOV	R0,R0		;GET FLAGS CORRECT
	RTS	PC
ROUTINE CPFY
	MOV	R2,-(SP)	
	MOV	SIDLST,R2	;LIST OF ALL SIZES
	BEQ	CPFYXX		;NULL LIST??
CPFYLP:	TST	GCFG(R2)	;COLLECTABLE??
	BEQ	CPFYNX		;BR IF NOT
	CALL	CPFYSP,<R2>	;COMPACTIFY THIS SPACE
CPFYNX:	MOV	NXTSID(R2),R2
	BNE	CPFYLP
CPFYXX:	CALL	MAPPTR,<#MUNLNK> ;MUNCH ALL LINKS
	; **** HERE IS THE SPOT WHERE YOU SHOULD WORRY ABOUT
	;      GETTING RID OF EXCESS BUFFER BLOCKS ****
CPFYRT:	MOV	(SP)+,R2	;RETURN
	RTS	RF

MUNLNK:	MOV	(R0),R1		;CALLED WITH R0 →→ A PTR
	TST	TAG(R1)		;DID WE MOVE IT ??
	BNE	MUNRTS		;
	MOV	WORD0(R1),(R0)	;YES, PUT NEW POINTER IN PLACE
MUNRTS:	RTS	PC		;

ROUTINE SWEEP
	MOV	R2,-(SP)	;
	MOV	SIDLST,R2	;LIST OF SIZES
	BEQ	SWP.X
SWP.LP:	JSR	PC,SWP.		;GO SWEEP ONE AREA
	MOV	NXTSID(R2),R2	;ITERATE
	BNE	SWP.LP		;
SWP.X:	MOV	(SP)+,R2	;
	RTS	RF		;

ROUTINE SWEEP1,<SPCC>	
	MOV	R2,-(SP)	;SAVE REGISTERS
	MOV	SPCC(RF),R2	;GET A SPACE
	JSR	PC,SWP.		;SWEEP ONE AREA
SWP.XX:	MOV	(SP)+,R2	
	RTS	RF

SWP.:	TST	GCFG(R2)	;IS THIS SPACE FOR SWEEPING??
	BNE	SWP.00		;
	RTS	PC		;NO
SWP.00:	MOV	R3,-(SP)	;YES
	MOV	R4,-(SP)	;
	CLR	FFREE(R2)	;WILL BUILD A REAL FREE LIST
	CLR	NFREE(R2)	;SINCE WE WILL FIX COUNTS
	CLR	NALLOC(R2)	;
	MOV	FSTBUF(R2),R3	;OLDEST BUFFER
	BEQ	SWP.3		;IF ANY
	MOV	SIZE(R2),R4	;COMPUTE SIZE
	INC	R4		;IN BYTES OF WHOLE THING
	ASL	R4		;
SWP.01:	MOV	FSTBLK(R3),R0	;GET A BLK
SWP.1:	TSTB	TAG(R0)		;ALLOCATED?
	BEQ	SWP.1N		;NO
	INC	NALLOC(R2)	;YES
	BR	SWP.2
SWP.1N:	INC	NFREE(R2)	;LINK UP A FREE
	MOV	FFREE(R2),WORD0(R0)
	MOV	R0,FFREE(R2)
SWP.2:	ADD	R4,R0		;BUMP POINTER TO NEXT IN BUFFER
	CMP	R0,LSTBLK(R3)	;DONE BUFFER??
	BLE	SWP.1		;NO
	MOV	NXTBUF(R3),R3	;YES GO ON TO NEXT
	BNE	SWP.01		;IF THERE IS ONE
SWP.3:	CMP	NFREE(R2),NMIN(R2)	;NEED MORE??
	BGT	SWP.5		;AT LEAST HAVE MIN NUMBER
SWP.4:	CALL	ADDBUF,<R2>	;NO, ADD A BUFFER FULL
	BR	SWP.3		;AND TRY AGAIN
SWP.5:	MOV	NFREE(R2),R0	;SEE IF HIGH ENOUGH PERCENTAGE
	ADD	NALLOC(R2),R0	;OF FREES
	MUL	NPCT(R2),R0	; 
	DIV	#144,R0		; NPCT*(NFREE+NALLOC)/=100
	CMP	NFREE(R2),R0	;
	BGT	SWP.6		;IF DONT HAVE ENOUGH
	CALL	ADDBUF,<R2>	;GET A BUFFER LOAD
	BR	SWP.5		;AND TRY AGAIN
SWP.6:	MOV	(SP)+,R4	;RESTORE
	MOV	(SP)+,R3
	RTS	PC

ROUTINE GC
	CALL	MARKPH		;MARK EVERYONE
	TST	CPFYOK		;IF DONT WANT COMPACTIFICATION
	BEQ	SWPPIT		;THEN DONT DO IT
	CALL	CPFY		;COMPACTIFY
SWPPIT:	CALL	SWEEP		;SWEEP UP LOOSE GARBAGE
	RTS	RF
; GETSBK, GETBLK

GETSBK:	
;
;	MOV	[SPACE DESCRIPTOR],R0
;	JSR	PC,GETSBK
;	<RETURNS WITH A BLOCK IN R0>
;
	MOV	R0,R1			
GETBL1:	TST	R1			;ERROR TRAP
	BEQ	GETBER
	MOV	FFREE(R1),R0		;R0 ← FIRST FREE
	BNE	GETBLX			;DID WE GET ONE
	MOV	R1,-(SP)		;NO,
	TST	GCFG(R1)		;IS GC OK FOR THIS AREA?
	BEQ	GETADB			;NO, MUST ADD
	TST	GCOK			;IS GARBAGE COLLECTION OK AT ALL
	BNE	GETGC			;
GETADB:	CALL	ADDBUF,<R1>		;NO, JUST GET A BUFFER
	BR 	GETBXX			;
GETGC:	CALL	GC			;YES, GC
GETBXX:	MOV	(SP)+,R1		;
	BR	GETBL1
GETBLX:	MOV	WORD0(R0),FFREE(R1)	;NEW FREE LIST
	INC	NALLOC(R1)		;ADJUST COUNTS
	DEC	NFREE(R1)
	MOVB	IDFLAG(R1),TAGID(R0)	;REMEMBER WHAT IT IS
	MOV	R0,-(SP)		;SAVE POINTER TO BLOCK
	MOV	SIZE(R1),R1		;WORD COUNT
GETB.C:	CLR	(R0)+			;CLEAR A WORD
	DEC	R1			;COUNT DOWN
	BGT	GETB.C			;UNTIL DONE
	MOV	(SP)+,R0		;RETURN VALUE BACK
	RTS	PC

;
;	MOV	#ID,R0
;	JSR	PC,GETBLK
;
GETBLK:	JSR	PC,GETSID		;SET UP SPC DSCR IN R1
	BR	GETBL1

GETBER:	HALERR	GERMSG
	CLR	R0
	RTS	PC

GERMSG:	ASCIE	/ATTEMPT TO ALLOCATE RECORD WITHOUT GIVING DESCRIPTOR/

GETSID:	MOV	R0,R1
	CMP	R0,#MAXIDF		;IN THE TABLE?
	BGT	GETS.1			;NO
	ASL	R1
	MOV	SIDTBL(R1),R1		;YES
GETS.X:	RTS	PC			;
GETS.1:	MOV	SIDLST,R1		;SEARCH CHAIN
	BEQ	GETS.X
GETS.2:	CMP	R0,IDFLAG(R1)		;THIS ONE??
	BNE	GETS.X			;YES
	MOV	NXTSID(R1),R1		;NO, TRY NEXT
	BNE	GETS.2
	RTS	PC

PTRSID:	MOV	R0,-(SP)		;SINCE GETSID WILL MUNCH
	MOVB	TAGID(R0),R0		;THE ID FLAG
	JSR	PC,GETSID		;GET SID INTO R1
	MOV	(SP)+,R0		;GET PTR BACK
	RTS	PC
; FREBLK, FRESBK

;	MOV	BLK,R0
;	JSR	PC,FREBLK

FREBLK: MOV	SIDLST,R1	;FIND THE SPACE
	BEQ	FREBER		;THIS CAME FROM
FREB.1:	CMPB	TAGID(R0),IDFLAG(R1) ;WAS IT THIS AREA
	BNE	FREB.2		;NO
FREB.:	MOV	FFREE(R1),WORD0(R0);FOUND THE AREA, PUT ON FREE CHAIN
	MOV	R0,FFREE(R1)
	INC	NFREE(R1)	;ADJUST COUNTS
	DEC	NALLOC(R1)
	CLRB	TAG(R0)		;JUST FOR RANDOMNESS
	RTS	PC		;DONE
FREB.2:	MOV	NXTSID(R1),R1	;LOOK AT NEXT
	BNE	FREB.1		;ITERATE
FREBER:	HALERR	FRERMS
FRERMS:	ASCIE	/ATTEMPT TO DELETE A BLOCK FROM AN AREA I CANNOT FIND/
	RTS	PC

FRESBK:	CMPB	TAGID(R0),IDFLAG(R1)	;BE SURE THIS IS OK
	BEQ	FREB.		;WE WIN
	HALERR	FRBER2
	BR	FREB.		;DO IT ANYHOW IF CONTINUES IT

FRBER2:	ASCIE	/ID DISAGREEMENT FOR FRESBK/
; NEWSPC, SETSPC

COMMENT ⊗ Create a space descriptor.  SZ is the size, IDF the IDFLAG,
NPB the number of blocks per buffer, GCF is set if the area is not to
be collected, NMN is the minimum number of free blocks that GC should
return, NPC is the minimum percent of free blocks that GC should
return.  R0 returns the address of the new space descriptor.  ⊗
ROUTINE NEWSPC,<SZ,IDF,NPB,GCF,NMN,NPC>

	MOV	#SPCHDR/2,R0	;GET A BLOCK OF CORE
	JSR 	PC,GTFREE
	MOV	SZ(RF),SIZE(R0) ;REMEMBER HOW BIG
	MOV	NPB(RF),NPERB(R0) ;
	MOV	IDF(RF),IDFLAG(R0) ;
	MOV	NMN(RF),NMIN(R0);
	MOV	NPC(RF),NPCT(R0);
NEWS.1:	MOV	SIDLST,NXTSID(R0)  ;LINK ONTO ID CHAIN
	MOV	R0,SIDLST
	MOV	IDFLAG(R0),R1	;WILL IT FIT IN ID CHAIN
	CMP	R1,#MAXIDF	;WILL IT FIT INTO TABLE
	BGT	NEWS.2		;
	ASL	R1		;YES
	MOV	R0,SIDTBL(R1)	;PUT INTO TABLE
NEWS.2:	CLR	FSTBUF(R0)	;ZERO OUT OTHER THINGS
	CLR	LSTBUF(R0)	;
	CLR	NALLOC(R0)
	CLR	NFREE(R0)
	RTS	RF		;RETURN

COMMENT ⊗ Initialize a space descriptor.  SPCADR is its address.  It
will be linked into the ID chanin, put in the SIDTBL if it fits, and
it will be cleared of all buffers.  ⊗
ROUTINE SETSPC,<SPCADR>
	MOV	SPCADR(RF),R0	;
	BR	NEWS.1		;GO INITIALIZE ALL NON-CONSTANT THINGS
ROUTINE ADDBUF,<SPACE>
;ADDS ANOTHER BUFFER TO THE NAMED SPACE
	MOV	R2,-(SP)		;SAVE A REGISTER
	MOV	R3,-(SP)
	MOV	SPACE(RF),R2
	MOV	SIZE(R2),R1		;CALCULATE WORD REQUIREMENTS
	INC	R1			;ONE WORD OVERHEAD FOR TAG & ID BYTES
	MOV	R1,-(SP)		;WILL NEED THIS LATER
	MUL	NPERB(R2),R1		;SIZE*NUMBER OF BLOCKS
	ADD	#BUFHDR/2,R1		;
	MOV	R1,R0			;
	JSR	PC,GTFREE		;GET A BLOCK
	MOV	LSTBUF(R2),R1		;LINK ONTO CHAIN
	MOV	R1,PRVBUF(R0)		;LINK BACK
	BEQ	ADB.01			;
	MOV	R0,NXTBUF(R1)		;AND PERHAPS FORWARD
	BR	ADB.1			;
ADB.01:	MOV	R0,FSTBUF(R2)		;IF WAS NO LSTBUF, THEN THIS IS FSTBUF
ADB.1:	CLR	NXTBUF(R0)		;CLEAN UP
	MOV	R0,LSTBUF(R2)		;NEW NEWEST BLOCK
	MOV	R0,R3			;
	ADD	#2+BUFHDR,R3		;POINTER AT FIRST BLOCK
	MOV	R3,FSTBLK(R0)		;REMEMBER IT
	MOV	NPERB(R2),R1		;
	ASL	(SP)			;NUMBER OF BYTES TO STEP BY
	SUB	(SP),R3			;TO UNDO FIRST ADD

ADB.2:	ADD	(SP),R3
	INC	NFREE(R2)		;ONE MORE FREE
	CLRB	TAG(R3)			;CLEAR TAG
	MOVB	IDFLAG(R2),TAGID(R3)	;SET TYPE ID
	MOV	FFREE(R2),WORD0(R3)	;CONS ONTO FREE LIST
	MOV	R3,FFREE(R2)		;
	DEC	R1			;ITERATE
	BGT	ADB.2			;IF ANY LEFT

	MOV	R3,LSTBLK(R0)		;R3 NOW POINTS AT LAST BLOCK
	TST	(SP)+			;POP
	MOV	(SP)+,R3		;RESTORE ACS
	MOV	(SP)+,R2
	RTS	RF

; Standard spaces, SBINIT

;Recall that MACRO DEFSPC ID,MMRT,SZ,NPB,GCF,NMN,NPC

SCASPC:	DEFSPC	VCTID,MKRTJM,2,10,0,4,15
VCTSPC:	DEFSPC	VCTID,MKRTJM,10,10,0,4,15
TRNSPC:	DEFSPC	VCTID,MKRTJM,40,4,0,2,15
CELSPC:	DEFSPC	VCTID,MKRTJM,2,10,1,4,15

ROUTINE SBINIT
; Initializes the small block allocator with the standard spaces.
	CLR	SIDLST
	CLR	GCOK
	CLR	CPFYOK
	CLR	MMETHS
	CALL	SETSPC,<#SCASPC>
	CALL	SETSPC,<#VCTSPC>
	CALL	SETSPC,<#TRNSPC>
	CALL	SETSPC,<#CELSPC>
	RTS	RF

.IFNZ	SMBDBG		;Test routine


FSTEST:	CALL	SBINIT
	MOV	#20,R2
	MOV	#VCTARA,R3
FST.1:	MOV	#VCTID,R0
	JSR	PC,GETBLK
FST.2:	MOV	R0,(R3)+
	DEC	R2
	BGT	FST.1
FST.3:	MOV	#13,R2
FST.4:	MOV	-(R3),R0
	JSR	PC,FREBLK
	DEC	R2
	BGT	FST.4
FST.5:	MOV	#17,R2
FST.6:	MOV	#VCTID,R0
	JSR	PC,GETBLK
	MOV	R0,(R3)+
	DEC	R2
	BGT	FST.6
FST.10:	MOV	#TSTMTH,R0
	JSR	PC,LNKMTH
	MOV	R3,VCTUB
	SUB	#2,VCTUB
	MOV	#VCTARA,VCTLB
	MOV	#-1,GCOK
	CALL	GC
FST.11:	MOV	#10,R2
FST.12:	MOV	#VCTSPC,R0
	JSR	PC,GETSBK
	DEC	R2
	BGT	FST.12

	HALERR	DNMSG

DNMSG:	ASCIE	</
WELL HOW DID WE DO?/>

VCTARA:	.BLKW	200
VCTUB:	0
VCTLB:	0

TSTMTH:	MMETH	TSTRTN

ROUTINE TSTRTN,<RTN>
	MOV	R2,-(SP)
	MOV	VCTLB,R2
TST.R1:	CMP	R2,VCTUB
	BGT	TSTRTS
	MOV	(R2),R0
	JSR	PC,MARKR0
	MOV	R0,(R2)+
	BR	TST.R1
TSTRTS:	MOV	(SP)+,R2
	RTS	RF

.ENDC